<?php

namespace api\components\utils;


class MapUtil
{
    /**
     * 边界坐标
     * @var array
     */
    public $coordArr=[];
    /**
     * @var array  lng 集合
     */
    public $vertx = [];
    /**
     * @var array lat 集合
     */
    public $verty = [];

    /**
     * MapUtil constructor.
     * @param mixed $coord  string lng,lat;lng,lat ...  or array [[lng,lat], [lng, lat],...]
     */
    public function __construct($coord)
    {
        if(is_array($coord)){
            array_walk($coord, function (&$item){
                $item = new Point($item[0], $item[1]);
            });
        }else{
            $coord = explode(';', $coord);
            $coord = array_filter($coord);
            array_walk($coord, function (&$item){
                $p = explode(',', $item);
                if(count($p)<2){
                    return false;
                }
                $item = new Point($p[0], $p[1]);
            });
        }
        $this->coordArr = $coord;
    }

    /**
     * 一个区域是否在一个区域内
     * @param mixed $points 被测试区域边界顶点 array string
     * @param bool $coordContain  是否包含边界点
     * @return bool
     */
    public function areaContain($points, $coordContain=true)
    {
        if(is_array($points)){
            array_walk($points, function (&$item){
                $item = new Point($item[0], $item[1]);
            });
        }else{
            $points = explode(';', $points);
            array_walk($points, function (&$item){
                $p = explode(',', $item);
                $item = new Point($p[0], $p[1]);
            });
        }
        foreach ($points as $point) {
            if(!$this->isContain($point)){
                //有一个点不在区域内则一定不在区域内
                return false;
            }
        }
        //所有顶点都在区域内 判定包含关系成立
        return true;
    }

    /**
     * 点是否在边界区域内
     * @param Point $point 被测试点
     * @param bool $coordContain  是否包含边界点
     * @return bool
     */
    public function isContain($point, $coordContain=true)
    {
        if(!($point instanceof Point)){
            list($lng,$lat) = explode(',', $point);
            $point = new Point($lng, $lat);
        }
        if($coordContain && $this->inCoordArr($point)){
            return true;
        }
        if(!$this->validatePoint($point)){
            return false;
        }
        return $this->pnpoly(count($this->coordArr), $point->lng, $point->lat);
    }

    /**
     * 判断是否在边界上
     * @param $point
     * @return bool
     */
    protected function inCoordArr($point)
    {
        foreach ($this->coordArr as $pointOne){
            if($pointOne->equal($point)){
                return true;
            }
        }
        return false;
    }
    /**
     * 验证点是否在 最大和最小经度纬度组成的四边形内
     * @param $point
     * @return bool
     */
    protected function validatePoint($point)
    {
        if (!$this->coordArr) {
            return false;
        }
        foreach ($this->coordArr as $item){
            $this->vertx[] = $item->lng;
            $this->verty[] = $item->lat;
        }
        $minX = min($this->vertx);
        $maxX = max($this->vertx);
        $minY = min($this->verty);
        $maxY = max($this->verty);
        if ($point->lng < $minX || $point->lng > $maxX || $point->lat < $minY || $point->lat > $maxY) {
            return false;
        }
        return true;
    }

    /**
     * 判断点是否在边界范围内
     * @param $nvert
     * @param $testx
     * @param $testy
     * @return bool
     */
    protected function pnpoly($nvert, $testx, $testy)
    {
        $c = false;
        for ($i = 0, $j = $nvert-1; $i < $nvert; $j = $i++) {
            if ( ( ($this->verty[$i]> $testy) != ($this->verty[$j]>$testy) ) && ($testx < ($this->vertx[$j]- $this->vertx[$i]) * ($testy-$this->verty[$i]) / ($this->verty[$j]-$this->verty[$i]) + $this->vertx[$i]) )
                $c = !$c;
        }
        return $c;
    }

    /**
     * 检查边界面积
     * 查过最大面积返回false不合理
     * @param int $maxArea 最大面积 2km*1km  2000000平方米
     * @return boolean   是否超过最大面积
     */
    public function checkArea($maxArea = 2000000)
    {
        return $this->calculateArea() <= $maxArea;
    }
    /**
     * 近似计算面积
     * @return int|number
     */
    public function calculateArea()
    {
        $point_num = count($this->coordArr);
        if ($point_num < 3) return 0;
        $s = $this->coordArr[0]->lat * ($this->coordArr[$point_num - 1]->lng - $this->coordArr[1]->lng);
        for ($i = 1; $i < $point_num; $i++) {
            $s += $this->coordArr[$i]->lat * ($this->coordArr[$i - 1]->lng - $this->coordArr[($i + 1) % $point_num]->lng);
        }
        return abs($s / 2.0)* 100000* 100000;
    }
    
    /**
     * 点是否在边界区域内
     * @param Point $point 被测试点
     * @return bool
     */
    public function isInside($point)
    {
        if(!($point instanceof Point)){
            list($lng,$lat) = explode(',', $point);
            $point = new Point($lng, $lat);
        }
        if(!$this->validatePoint($point)){
            return false;
        }
        return $this->isPointInPolygon(count($this->coordArr), $point->lng, $point->lat);
    }
    
    /**
     * @param $num
     * @param $x    lng
     * @param $y    lat
     * @return bool
     */
    protected function isPointInPolygon($num, $x, $y)
    {
        $bInside = false;
        for ($i = 0, $j = $num-1; $i < $num; $j = $i++) {
            //向左侧发射射线验证
            if ((($this->verty[$i] < $y && $this->verty[$j] >= $y)
                    || ($this->verty[$i] >= $y && $this->verty[$j] < $y))
                && ($this->vertx[$i] <= $x || $this->vertx[$j] <= $x)) {
                $iTmp = $this->vertx[$i] + ($y-$this->verty[$i])/($this->verty[$j]-$this->verty[$i])*($this->vertx[$j]-$this->vertx[$i]);
                if($iTmp == $x) {//点在多边形的边上
                    return true;
                } else
                    $bTmp = ($iTmp < $x);
                $bInside = ($bInside xor $bTmp);
            }
        }
        return $bInside;
    }
}
